#include <algorithm/analytical_model/algorithm/Distance.h>
#include <algorithm/analytical_model/algorithm/FocusCenter.h>
#include <algorithm/analytical_model/algorithm/geometry/AllGeometry.h>
#include <algorithm/analytical_model/algorithm/geometry/Utils.h>
#include <algorithm/shared/data/topology/geometry/Cylinder3D.h>
#include <algorithm/shared/data/topology/geometry/Geometry.h>
#include <algorithm/shared/data/topology/geometry/Rectangle.h>
#include <algorithm/shared/data/topology/geometry/Rectangle3D.h>
#include <utils/Format.h>
#include <utils/ParaUtils.h>
#include <utils/Print.h>
#include <utils/config.h>

using namespace solar;

auto solar::angleToDirection(double azimuth, double elevation) -> Vec3
{
    const double azimuth_radian = solar::degreeToRadian(azimuth);
    const double elevation_radian = solar::degreeToRadian(elevation);
    return radianToDirection(azimuth_radian, elevation_radian);
}

auto solar::radianToDirection(double azimuth_radian, double elevation_radian) -> Vec3
{
    return (-Vec3(-std::cos(elevation_radian) * std::sin(azimuth_radian),
                  -std::sin(elevation_radian),
                  std::cos(elevation_radian) * std::cos(azimuth_radian)))
        .get_unit_vector();
}

auto solar::projectTo(const Vec3& vec, const Vec3& normal) -> Vec3
{
    const auto unit_normal = normal.get_unit_vector();
    return vec - solar::dot(vec, unit_normal) * normal;
}

auto solar::intersect(const Vec3& normal, const Vec3& plane_origin, const Vec3& vertex,
                      const Vec3& direction) -> std::tuple<Vec3, double>
{
    const auto div = solar::dot(direction, normal);
    const auto direction_t = solar::dot(plane_origin - vertex, normal) / div;
    return {vertex + direction_t * direction, direction_t};
}

auto solar::getDistanceFromHeliostatToReceiver(Geometry* geometry_heliostat,
                                               Geometry* geometry_receiver) -> double
{
    const auto geometry_heliostat_type = geometry_heliostat->getType();
    const auto geometry_receiver_type = geometry_receiver->getType();
    switch (geometry_heliostat_type)
    {
    case solar::GeometryType::kGeometryRectangle3D:
    {
        switch (geometry_receiver_type)
        {
        case solar::GeometryType::kGeometryRectangle3D:
            return solar::distance(dynamic_cast<Rectangle3D*>(geometry_heliostat),
                                   dynamic_cast<Rectangle3D*>(geometry_receiver));
        case solar::GeometryType::kGeometryCylinder3D:
            return solar::distance(dynamic_cast<Rectangle3D*>(geometry_heliostat),
                                   dynamic_cast<Cylinder3D*>(geometry_receiver));

        case GeometryType::kGeometryRectangle:
            [[fallthrough]];
        case GeometryType::kGeometryParabolic:
        case GeometryType::kGeometryHyperbolic:
        case GeometryType::kGeometryElliptic:
        case GeometryType::kGeometryBSpline:
        case GeometryType::kGeometryMesh:
        case GeometryType::kGeometryCylinder:
        }
    }
        [[fallthrough]];
    case GeometryType::kGeometryRectangle:
    case GeometryType::kGeometryParabolic:
    case GeometryType::kGeometryHyperbolic:
    case GeometryType::kGeometryElliptic:
    case GeometryType::kGeometryBSpline:
    case GeometryType::kGeometryMesh:
    case GeometryType::kGeometryCylinder:
    default:
    {
        const auto error_string =
            ::solar::format("ERROR: In function {}:\n ({}, {}) is not implemented",
                            PRETTY_FUNCTION_NAME, solar::getEnumName(geometry_heliostat_type),
                            solar::getEnumName(geometry_receiver_type));
        solar::println(error_string);
        throw error_string;
    }
    }
}

auto solar::getFocusCenter(Geometry* geometry_heliostat, Geometry* geometry_receiver) -> Vec3
{
    const auto geometry_heliostat_type = geometry_heliostat->getType();
    const auto geometry_receiver_type = geometry_receiver->getType();
    switch (geometry_heliostat_type)
    {
    case solar::GeometryType::kGeometryRectangle3D:
    {
        switch (geometry_receiver_type)
        {
        case solar::GeometryType::kGeometryRectangle3D:
            return solar::focusCenter(dynamic_cast<Rectangle3D*>(geometry_heliostat),
                                      dynamic_cast<Rectangle3D*>(geometry_receiver));
        case solar::GeometryType::kGeometryCylinder3D:
            return solar::focusCenter(dynamic_cast<Rectangle3D*>(geometry_heliostat),
                                      dynamic_cast<Cylinder3D*>(geometry_receiver));
        case GeometryType::kGeometryRectangle:
            [[fallthrough]];
        case GeometryType::kGeometryParabolic:
        case GeometryType::kGeometryHyperbolic:
        case GeometryType::kGeometryElliptic:
        case GeometryType::kGeometryBSpline:
        case GeometryType::kGeometryMesh:
        case GeometryType::kGeometryCylinder:
        }
    }
        [[fallthrough]];
    case solar::GeometryType::kGeometryCylinder3D:
    case GeometryType::kGeometryRectangle:
    case GeometryType::kGeometryParabolic:
    case GeometryType::kGeometryHyperbolic:
    case GeometryType::kGeometryElliptic:
    case GeometryType::kGeometryBSpline:
    case GeometryType::kGeometryMesh:
    case GeometryType::kGeometryCylinder:
    default:
    {
        const auto error_string =
            ::solar::format("ERROR: In function {}:\n ({}, {}) is not implemented",
                            PRETTY_FUNCTION_NAME, solar::getEnumName(geometry_heliostat_type),
                            solar::getEnumName(geometry_receiver_type));
        solar::println(error_string);
        throw error_string;
    }
    }
}

auto solar::getCenter(Geometry* input) -> Vec3
{
    const auto input_type = input->getType();
    switch (input_type)
    {
    case solar::GeometryType::kGeometryRectangle3D:
        return solar::center(dynamic_cast<Rectangle3D*>(input));
    case solar::GeometryType::kGeometryCylinder3D:
        return solar::center(dynamic_cast<Cylinder3D*>(input));
    case GeometryType::kGeometryRectangle:
        [[fallthrough]];
    case GeometryType::kGeometryParabolic:
    case GeometryType::kGeometryHyperbolic:
    case GeometryType::kGeometryElliptic:
    case GeometryType::kGeometryBSpline:
    case GeometryType::kGeometryMesh:
    case GeometryType::kGeometryCylinder:
    default:
    {
        const auto error_string =
            ::solar::format("ERROR: In function {}:\n {} is not implemented", PRETTY_FUNCTION_NAME,
                            solar::getEnumName(input_type));
        solar::println(error_string);
        throw error_string;
    }
    }
}

auto solar::getNormal(Geometry* input) -> Vec3
{
    const auto input_type = input->getType();
    switch (input_type)
    {
    case solar::GeometryType::kGeometryRectangle3D:
        return solar::normal(dynamic_cast<Rectangle3D*>(input));
    case solar::GeometryType::kGeometryCylinder3D:
        [[fallthrough]];
    case GeometryType::kGeometryRectangle:
    case GeometryType::kGeometryParabolic:
    case GeometryType::kGeometryHyperbolic:
    case GeometryType::kGeometryElliptic:
    case GeometryType::kGeometryBSpline:
    case GeometryType::kGeometryMesh:
    case GeometryType::kGeometryCylinder:
    default:
    {
        const auto error_string =
            ::solar::format("ERROR: In function {}:\n {} is not implemented", PRETTY_FUNCTION_NAME,
                            solar::getEnumName(input_type));
        solar::println(error_string);
        throw error_string;
    }
    }
}

auto solar::getArea(Geometry* input) -> double
{
    const auto input_type = input->getType();
    switch (input_type)
    {
    case solar::GeometryType::kGeometryRectangle:
        return solar::area(dynamic_cast<Rectangle*>(input));
    case solar::GeometryType::kGeometryRectangle3D:
        return solar::area(dynamic_cast<Rectangle3D*>(input));
    case solar::GeometryType::kGeometryCylinder3D:
        [[fallthrough]];
    case GeometryType::kGeometryParabolic:
    case GeometryType::kGeometryHyperbolic:
    case GeometryType::kGeometryElliptic:
    case GeometryType::kGeometryBSpline:
    case GeometryType::kGeometryMesh:
    case GeometryType::kGeometryCylinder:
    default:
    {
        const auto error_string =
            ::solar::format("ERROR: In function {}:\n {} is not implemented", PRETTY_FUNCTION_NAME,
                            solar::getEnumName(input_type));
        solar::println(error_string);
        throw error_string;
    }
    }
}
